﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;

namespace UniversalApproach.Expands
{
    public static class LinqExpand
    {
        public static PropertyInfo GetPropertyInfo(Type objType, string name)
        {
            var properties = objType.GetProperties();
            var matchedProperty = properties.FirstOrDefault(p => p.Name == name);
            if (matchedProperty == null)
                throw new ArgumentException("name");

            return matchedProperty;
        }
        private static LambdaExpression GetOrderExpression(Type objType, PropertyInfo pi)
        {
            var paramExpr = Expression.Parameter(objType);
            var propAccess = Expression.PropertyOrField(paramExpr, pi.Name);
            var expr = Expression.Lambda(propAccess, paramExpr);
            return expr;
        }

        #region Where
        public static IEnumerable<T> Where<T>(this IEnumerable<T> query, string name, string val)
        {
            var p = typeof(T).GetProperty(name);
            if (p == null)
            {
                return query;
            }

            return query.Where(s => val == p.GetValue(s).ToString());
        }
        #endregion

        #region OrderBy
        public static IEnumerable<T> OrderBy<T>(this IEnumerable<T> query, string name)
        {
            var propInfo = GetPropertyInfo(typeof(T), name);
            var expr = GetOrderExpression(typeof(T), propInfo);

            var method = typeof(Enumerable).GetMethods().FirstOrDefault(m => m.Name == "OrderBy" && m.GetParameters().Length == 2);
            var genericMethod = method.MakeGenericMethod(typeof(T), propInfo.PropertyType);
            return (IEnumerable<T>)genericMethod.Invoke(null, new object[] { query, expr.Compile() });
        }

        public static IList<IGrouping<object[], T>> OrderBy<T>(this IList<IGrouping<object[], T>> query, string name)
        {
            return query.OrderBy(f =>
            {
                return f.Key.GetType().GetProperty(name);
            }).ToList();
        }

        public static IQueryable<T> OrderBy<T>(this IQueryable<T> query, string name)
        {
            var propInfo = GetPropertyInfo(typeof(T), name);
            var expr = GetOrderExpression(typeof(T), propInfo);

            var method = typeof(Queryable).GetMethods().FirstOrDefault(m => m.Name == "OrderBy" && m.GetParameters().Length == 2);
            var genericMethod = method.MakeGenericMethod(typeof(T), propInfo.PropertyType);
            return (IQueryable<T>)genericMethod.Invoke(null, new object[] { query, expr });
        }

        #endregion

        #region OrderByDescending
        public static IEnumerable<T> OrderByDescending<T>(this IEnumerable<T> query, string name)
        {
            var propInfo = GetPropertyInfo(typeof(T), name);
            var expr = GetOrderExpression(typeof(T), propInfo);

            var method = typeof(Enumerable).GetMethods().FirstOrDefault(m => m.Name == "OrderByDescending" && m.GetParameters().Length == 2);
            var genericMethod = method.MakeGenericMethod(typeof(T), propInfo.PropertyType);
            return (IEnumerable<T>)genericMethod.Invoke(null, new object[] { query, expr.Compile() });
        }

        public static IQueryable<T> OrderByDescending<T>(this IQueryable<T> query, string name)
        {
            var propInfo = GetPropertyInfo(typeof(T), name);
            var expr = GetOrderExpression(typeof(T), propInfo);

            var method = typeof(Queryable).GetMethods().FirstOrDefault(m => m.Name == "OrderByDescending" && m.GetParameters().Length == 2);
            var genericMethod = method.MakeGenericMethod(typeof(T), propInfo.PropertyType);
            return (IQueryable<T>)genericMethod.Invoke(null, new object[] { query, expr });
        }

        #endregion

        #region GroupBy
        public static IEnumerable<IGrouping<string, T>> GroupBy<T>(this IEnumerable<T> query, string name)
        {
            var propInfo = GetPropertyInfo(typeof(T), name);
            var expr = GetOrderExpression(typeof(T), propInfo);

            var method = typeof(Enumerable).GetMethods().FirstOrDefault(m => m.Name == "GroupBy" && m.GetParameters().Length == 2);
            var genericMethod = method.MakeGenericMethod(typeof(T), propInfo.PropertyType);
            return (IEnumerable<IGrouping<string, T>>)genericMethod.Invoke(null, new object[] { query, expr.Compile() });
        }
        public static IQueryable<IGrouping<string, T>> GroupBy<T>(this IQueryable<T> query, string name)
        {
            var propInfo = GetPropertyInfo(typeof(T), name);
            var expr = GetOrderExpression(typeof(T), propInfo);
            var method = typeof(Enumerable).GetMethods().FirstOrDefault(m => m.Name == "GroupBy" && m.GetParameters().Length == 2);
            var genericMethod = method.MakeGenericMethod(typeof(T), propInfo.PropertyType);
            return (IQueryable<IGrouping<string, T>>)genericMethod.Invoke(null, new object[] { query, expr.Compile() });
        }



        public static IEnumerable<IGrouping<object[], T>> DynamicGroupBy<T>(this IEnumerable<T> data, string[] keys)
        {
            List<DGroupBy<T>> list = new List<DGroupBy<T>>();

            var exps = keys.Select(x =>
            {
                var para = Expression.Parameter(typeof(T), "p");
                Expression<Func<T, object>> GetProp = Expression.Lambda<Func<T, object>>(
                    Expression.MakeMemberAccess(para, typeof(T).GetProperty(x)), para) as Expression<Func<T, object>>;
                return GetProp.Compile();
            }).ToArray();

            foreach (var item in data.Select(x => new
            {
                k = exps.Select(y => y(x)).ToArray(),
                v = x
            }))
            {
                DGroupBy<T> existing = list.SingleOrDefault(x => x.Key.Zip(item.k, (a, b) => a.Equals(b)).All(y => y));
                if (existing == null)
                {
                    existing = new DGroupBy<T>(item.k);
                    list.Add(existing);
                }
                existing.Add(item.v);
            }
            return list;
        }
        #endregion



        #region Select
        public static object Select<T>(this IEnumerable<T> query, string name)
        {
            var propInfo = GetPropertyInfo(typeof(T), name);
            var expr = GetOrderExpression(typeof(T), propInfo);

            var method = typeof(Enumerable).GetMethods().FirstOrDefault(m => m.Name == "Select" && m.GetParameters().Length == 2);
            var genericMethod = method.MakeGenericMethod(typeof(T), propInfo.PropertyType);
            return genericMethod.Invoke(null, new object[] { query, expr.Compile() });
        }
        #endregion
        #region Sum
        public static double Sum<T>(this IGrouping<object[], T> query, string name)
        {
            var need = query.ToList().Select(name);
            var str = Newtonsoft.Json.JsonConvert.SerializeObject(need);
            var t = Newtonsoft.Json.JsonConvert.DeserializeObject<List<string>>(str);
            var a = 0d;
            foreach (var item in t)
            {
                a += Convert.ToDouble(item);
            }
            return a;
        }

        public static double Sum<T>(this IEnumerable<T> query, string name)
        {
            var need = query.Select(name);
            var str = Newtonsoft.Json.JsonConvert.SerializeObject(need);
            var t = Newtonsoft.Json.JsonConvert.DeserializeObject<List<string>>(str);
            var a = 0d;
            foreach (var item in t)
            {
                a += Convert.ToDouble(item);
            }
            return a;
        }
        #endregion
    }

    public class DGroupBy<T> : IGrouping<object[], T>
    {
        private List<T> _innerlist = new List<T>();

        private object[] _key;

        public DGroupBy(object[] key) { _key = key; }

        public object[] Key
        {
            get { return _key; }
        }

        public void Add(T value)
        {
            _innerlist.Add(value);
        }

        public IEnumerator<T> GetEnumerator()
        {
            return this._innerlist.GetEnumerator();
        }

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return this._innerlist.GetEnumerator();
        }
    }
}